LootWatch = {}
local items = 50
local idle = false
local color = {
	incompleteTitle = { 0.75, 0.61, 0 },
	completeTitle = { NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b },
	incompleteRow = { 0.8, 0.8, 0.8 },
	completeRow = { HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b },
	text = { 0.5, 0.5, 1.0, nil },
}
local helptext = {
	"LootWatch help:",
	"/lw # [item link] - add item to watch.",
	"/lw 0 [item link] - show total amount.",
	"",
	"/lw +# [item link] - add amount to watched item",
	"/lw -# [item link] - subtract amount from watched item",
	"/lw *# [item link] - multiply amount for watched item",
	"/lw /# [item link] - divide amount for watched item",
	"",
	"/lw clear [item link] - remove item from watch",
	"/lw clear all - remove all items from watch",
	"",
	"/lw move on|off|reset - start moving LootWatch frame.",
	"/lw fontsize # - change font size (default 11).",
	"/lw show - toggle LootWatch frame.",
	"/lw hide combat on|off - hide the frame when entering combat?",
	"/lw hide party on|off - hide the frame when in a group?",
	"/lw banks on|off - include items from banks in totals?",
	"/lw alts on|off - include items on alts in totals?",
}
local rowFormat = " - %s: %d"
local rowFormatAimed = " - %s: %d/%d"
local self = LootWatch
local db = nil
local opt = nil
local f = nil
local e = nil
local lastUpdateRequest
local bankavailable = false

function LootWatch:OnEvent(self, event, ...)
	if (event == "VARIABLES_LOADED") then
		if not(LootWatch_Global) then LootWatch_Global = {} end
		gdb = LootWatch_Global
		if not(LootWatch_DataPC) then LootWatch_DataPC = {} end
		db = LootWatch_DataPC
		if not(LootWatch_OptPC) then LootWatch_OptPC = {} end
		opt = LootWatch_OptPC
		
		local optdefaults = {
			fontSize = 11,
			vis = "on",
			hidecombat = "off",
			hideparty = "off",
			incbanks = "on",
			incalts = "on",
		}
		for k,v in pairs(optdefaults) do if not(opt[k]) then opt[k] = v end end
		
		if (opt.hidecombat == "on") then LootWatch_Command("hide combat on") end
		
		e:RegisterEvent("BAG_UPDATE")
		e:RegisterEvent("BANKFRAME_OPENED")
		e:RegisterEvent("BANKFRAME_CLOSED")
		
		idle = true
		for name, item in pairs(db) do
			item.amount = 0 -- reset some data after login/reload ui/crash
			item.counter = 0 -- add/reset counter for backward compatibility
			-- item.value = 0 -- reset gold values just like the others are reset
		end
		if (not f:IsUserPlaced()) then
			f:ClearAllPoints()
			f:SetPoint("CENTER", "UIParent", "CENTER", 0, 0)
		end
	end
	
	-- Hide/show when entering/leaving combat, if enabled
	if (event == "PLAYER_REGEN_DISABLED") then
		opt.vis = "off"
	end
	if (event == "PLAYER_REGEN_ENABLED") then
		opt.vis = "on"
	end
	
	-- If we're hiding when in a party/raid then do so if needed.
	if (event == "PARTY_MEMBERS_CHANGED") then
		if ((GetNumPartyMembers() > 0) or (GetNumPartyMembers() > 0)) then
			opt.vis = "off"
		else
			opt.vis = "on"
		end
	end
	
	if (event == "BANKFRAME_OPENED") then bankavailable = true end
	if (event == "BANKFRAME_CLOSED") then bankavailable = false end
	
	LootWatch:RequestUpdate()
end
function LootWatch:RequestUpdate()
	if (idle) then
		f:Show()
		idle = false
		if (not e:GetScript("OnUpdate")) then
			e:SetScript("OnUpdate", function()
				if (lastUpdateRequest and lastUpdateRequest + 0.4 < GetTime()) then
					lastUpdateRequest = nil
					idle = true
					e:SetScript("OnUpdate", nil)
					self:Update()
				elseif (not lastUpdateRequest) then
					lastUpdateRequest = GetTime()
					idle = false
				end
			end)
		end
	end
end
local function CopyTable(tbl)
	local copy = {} 
	local k,v
	for k,v in pairs(tbl) do
		if (type(v) == "table") then
			copy[k] = CopyTable(v)
		else
			copy[k] = v
		end
	end
	return copy
end
function LootWatch:Update()
	-- Set up global data if needed.
	local playername, _ = UnitName("player")
	if playername == UNKNOWNOBJECT then playername = nil end
	local realmname = GetRealmName()
	local faction
	if playername then
		faction, _ = UnitFactionGroup("player") -- Alliance, Horde
	end
	if playername and realmname and faction then
		if not gdb[realmname] then
			gdb[realmname] = {}
		end
		if not gdb[realmname][faction] then
			gdb[realmname][faction] = {}
		end
		-- Upgrade to faction divided table if needed
		if gdb[realmname][playername] then
			gdb[realmname][faction][playername] = CopyTable(gdb[realmname][playername])
			gdb[realmname][playername] = nil
		elseif not(gdb[realmname][faction][playername]) then
		-- Or create the table as new
			gdb[realmname][faction][playername] = {
				bags = {},
				bank = {},
			}
		end
	end
	
	-- initialize
	f.title:SetVertexColor(unpack(color.completeTitle))
	local width, row = 1
	local k,v
	-- Reset loot counts
	for k,v in pairs(db) do
		v.counter = 0
	end
	if realmname and playername and faction then
		local k,v
		for k,v in pairs(gdb[realmname][faction][playername].bags) do
			gdb[realmname][faction][playername].bags[k] = 0
		end
		-- If the bank frame is open, then we can zero those values to get a refresh as well.
		if bankavailable then
			for k,v in pairs(gdb[realmname][faction][playername].bank) do
				gdb[realmname][faction][playername].bank[k] = 0
			end
		end
	end

	-- Bag update
	local bag
	for bag = 0, NUM_BAG_FRAMES do
		local i
		for i = 1, GetContainerNumSlots(bag) do
			local link = GetContainerItemLink(bag, i)
			if (link) then
				local _, _, name = string.find(link, ".*%[(.+)%].*")
				local _, stack = GetContainerItemInfo(bag, i)
				if (db[name]) then
					db[name].counter = db[name].counter + stack
				end
				if playername and realmname and faction then
					gdb[realmname][faction][playername].bags[name] = (gdb[realmname][faction][playername].bags[name] or 0) + stack
				end
			end
		end
	end
	
	-- Keyring update
	local i
	for i = 1, GetContainerNumSlots(-2) do
		local link = GetContainerItemLink(-2, i)
		if (link) then
			local _, _, name = string.find(link, ".*%[(.+)%].*")
			local _, stack = GetContainerItemInfo(-2, i)
			if (db[name]) then
				db[name].counter = db[name].counter + stack
			end
			if playername and realmname and faction then
				gdb[realmname][faction][playername].bags[name] = (gdb[realmname][faction][playername].bags[name] or 0) + stack
			end
		end
	end
	
	-- Bank update
	if bankavailable then
		--DEFAULT_CHAT_FRAME:AddMessage("Scanning bank")
		-- Scan default bank
		local i
		for i = 1, 28 do
			local link = GetContainerItemLink(BANK_CONTAINER, i)
			if (link) then
				local _, _, name = string.find(link, ".*%[(.+)%].*")
				local _, stack = GetContainerItemInfo(BANK_CONTAINER, i)
				if playername and realmname and faction then
					--DEFAULT_CHAT_FRAME:AddMessage("Found item "..name.." "..stack)
					gdb[realmname][faction][playername].bank[name] = (gdb[realmname][faction][playername].bank[name] or 0) + stack
				end
			end
		end

		-- Scan bags in bank
		local bag
		for bag = 5, 11 do
			local i
			local slots = GetContainerNumSlots(bag)
			local link = nil
			for i = 1, slots do
				link = GetContainerItemLink(bag, i)
				if (link) then
					local _, _, name = string.find(link, ".*%[(.+)%].*")
					local _, stack = GetContainerItemInfo(bag, i)
					if playername and realmname and faction then
						gdb[realmname][faction][playername].bank[name] = (gdb[realmname][faction][playername].bank[name] or 0) + stack
					end
				end
			end
		end
	end
	
	-- Scan any items in global db
	for gdbplayername, gdbplayerdata in pairs(gdb[realmname][faction]) do
		for gdbitemname, gdbitemcount in pairs(gdbplayerdata.bank) do
			if opt.incbanks == "on" and opt.incalts == "on" then
				if db[gdbitemname] then db[gdbitemname].counter = db[gdbitemname].counter + gdbitemcount end
			elseif opt.incbanks == "on" and opt.incalts == "off" then
				if gdbplayername == playername then
					if db[gdbitemname] then db[gdbitemname].counter = db[gdbitemname].counter + gdbitemcount end
				end
			end
		end
		for gdbitemname, gdbitemcount in pairs(gdbplayerdata.bags) do
			-- Here, we skip checking bag counts for the current player, as the data is already loaded
			if (gdbplayername ~= playername) then
				if opt.incalts == "on" then
					if db[gdbitemname] then
						db[gdbitemname].counter = db[gdbitemname].counter + gdbitemcount
					end
				end
			end
		end
	end
	
	-- Clear out any zero-value items so they will not be displayed later
	for k,v in pairs(gdb[realmname][faction][playername].bags) do
		if (v == 0) then
			gdb[realmname][faction][playername].bags[k] = nil
		end
	end
	for k,v in pairs(gdb[realmname][faction][playername].bank) do
		if (v == 0) then
			gdb[realmname][faction][playername].bank[k] = nil
		end
	end


	local i = 1

	-- show all items
	for name, item in pairs(db) do
		row = f.row[i]
		if (not row) then break end -- out of XML elements! skip ahead
		if (item.goal == 1 and item.counter == 0) then
			db[name] = nil -- remove when an item is sold or lost
		else
			if (item.target > 0 and item.counter > item.amount and ((not (item.amount >= item.target)) or (item.count == item.target)) ) then
				-- emulate quest progress spam
				UIErrorsFrame:AddMessage(format(ERR_QUEST_ADD_ITEM_SII, name, (item.counter > item.target) and item.target or item.counter, item.target), 1.0, 1.0, 0, 1.0)
				if(item.counter >= item.target) then item.goal = 1; end
			end
			-- set the line and color
			row:SetFont("Fonts\\FRIZQT__.TTF", opt.fontSize, nil)
			row:SetText(format(item.target and item.target > 0 and rowFormatAimed or rowFormat, name, item.counter, item.target))
			row:SetVertexColor(unpack(item.target and item.counter < item.target and item.target > 0 and color.incompleteRow or color.completeRow))
			row.frame:SetScript("OnEnter", function(self, ...)
				GameTooltip:SetOwner(self,"ANCHOR_RIGHT")
				GameTooltip:AddLine(name)
				for k,v in pairs(gdb[realmname][faction]) do
					if opt.incalts == "on" then
						if v.bags[name] then GameTooltip:AddLine(k.." Bags: "..v.bags[name]) end
					elseif opt.incalts == "off" then
						if k == playername then
							if v.bags[name] then GameTooltip:AddLine(k.." Bags: "..v.bags[name]) end
						end
					end
					if opt.incbanks == "on" and opt.incalts == "on" then
						if v.bank[name] then GameTooltip:AddLine(k.." Bank: "..v.bank[name]) end
					elseif opt.incbanks == "on" and opt.incalts == "off" then
						if k == playername then
							if v.bank[name] then GameTooltip:AddLine(k.." Bank: "..v.bank[name]) end
						end
					end
				end
				GameTooltip:Show()
			end)
			row.frame:SetScript("OnLeave", function() GameTooltip:Hide() end)
			row.frame:SetHeight(row:GetHeight())
			row.frame:SetWidth(row:GetWidth())
			if opt.vis == "on" then row:Show() else row:Hide() end
			width = row:GetWidth() < width and width or row:GetWidth()
			if (item.target and item.counter < item.target and item.target > 0) then
				-- set the title incomplete if any line isn't complete
				f.title:SetVertexColor(unpack(color.incompleteTitle))
			end
			item.amount = item.counter
			item.counter = 0
			i = i + 1
		end
	end
	
	f.title:SetFont("Fonts\\FRIZQT__.TTF", opt.fontSize, nil)

	for i=i, items do -- hide the unused rows
		row = f.row[i]:Hide()
	end

	if (i == 1) then -- nothing to watch
		f:Hide()
		f.title:Hide()
		return
	else
		if opt.vis == "on" then f:Show(); f.title:Show() else f:Hide(); f.title:Hide() end
	end

	if (width > 1) then -- make same size as the quest watch frame
		f:SetWidth(width)
		f:SetHeight(i * f.row[1]:GetHeight() + 4)
		f:SetFrameLevel(0)
	end
end

-- slash command handler
function LootWatch_Command(cmd)
	-- hide in party
	local _, _, hideparty = string.find(cmd, "hide party ([onf]+)")
	if (hideparty) then
		if (hideparty == "on") then
			opt.hideparty = "on"
			-- Check and hide if player is already in a party
			if ((GetNumPartyMembers() > 0) or (GetNumPartyMembers() > 0)) then
				opt.vis = "off"
			end
			e:RegisterEvent("PARTY_MEMBERS_CHANGED")
		elseif (hideparty == "off") then
			opt.hideparty = "off"
			e:UnregisterEvent("PARTY_MEMBERS_CHANGED")
			-- turn frame back on
			opt.vis = "on"
			f:Show()
		end
		self:RequestUpdate()
		return
	end
	
	-- hide in combat
	local _, _, hidecombat = string.find(cmd, "hide combat ([onf]+)")
	if (hidecombat) then
		if (hidecombat == "on") then
			opt.hidecombat = "on"
			e:RegisterEvent("PLAYER_REGEN_DISABLED")
			e:RegisterEvent("PLAYER_REGEN_ENABLED")
		elseif (hidecombat == "off") then
			opt.hidecombat = "off"
			e:UnregisterEvent("PLAYER_REGEN_DISABLED")
			e:UnregisterEvent("PLAYER_REGEN_ENABLED")
		end
		self:RequestUpdate()
		return
	end
	
	-- include banks
	local _, _, incbanks = string.find(cmd, "banks ([onf]+)")
	if (incbanks) then
		if (incbanks == "on") then opt.incbanks = "on" elseif (incbanks == "off") then opt.incbanks = "off" end
		self:RequestUpdate()
		return
	end
	
	-- include alts
	local _, _, incalts = string.find(cmd, "alts ([onf]+)")
	if (incalts) then
		if (incalts == "on") then opt.incalts = "on" elseif (incalts == "off") then opt.incalts = "off" end
		self:RequestUpdate()
		return
	end
	
	-- font size
	local _, _, size = string.find(cmd, "fontsize (.*)")
	if (size) then opt.fontSize = tonumber(size); self:RequestUpdate(); return end
	
	-- hide
	local _, _, hide = string.find(cmd, "(show)")
	if (hide) then
		if opt.vis == "on" then opt.vis = "off" else opt.vis = "on" end
		self:RequestUpdate()
		return
	end
	
	-- clear item/all
	local _, _, item = string.find(cmd, "clear (.+)")
	if (item) then
		if (item == "all") then
			-- delete all items
			for k,_ in pairs(db) do
				db[k] = nil
			end
		else
			-- delete item from list
			_, _, item = string.find(item, ".*%[(.+)%].*")
			if (item) then
				db[item] = nil
			end
		end
		self:RequestUpdate()
		return
	end

	-- set amount
	_, _, sign, newTarget, item = string.find(cmd, "([-+*/]?)(%d+) +.*%[(.+)%].*")
	newTarget = tonumber(newTarget)
	if (item and newTarget) then
		if(sign ~= "") then
			if(db[item]) then
				-- is watched?
				if(db[item].target < 0) then
					if(sign ~= "+") then
						newTarget = -1
					end
				else
					-- modify values
					if(sign == "+") then
						newTarget = db[item].target + newTarget
					elseif (sign == "-") then
						newTarget = db[item].target - newTarget
						-- watch if negative or zero
						if(newTarget <= 0) then
							newTarget = -1
						end
					elseif (sign == "*") then
						newTarget = db[item].target * newTarget
						if(newTarget <= 0) then
							newTarget = -1
						end
					elseif (sign == "/") then
						-- division by zero?
						if(newTarget == 0) then
							newTarget = db[item].target
						else
							newTarget = math.floor(db[item].target / newTarget)
							if(newTarget <= 0) then
								newTarget = -1
							end
						end
					end
				end
			else
				-- trying to do something weird?
				if(sign ~= "+") then
					newTarget = -1
				end
			end
		end
		
		db[item] = { target = newTarget, goal = 0, amount = 0, counter = 0, }
		self:RequestUpdate()
	  return
	end
	
	-- /lw [item] implementation.
	_, _, watchitem = string.find(cmd, "%[(.+)%]")
	if (watchitem) then
		if (db[watchitem]) then
			db[watchitem].target = 0
		else
			db[watchitem] = { target = 0, goal = 0, amount = 0, counter = 0 }
		end
		self:RequestUpdate()
		return
	end
	
	-- move on/off
	local _, _, enable = string.find(cmd, "move ([onfrest]+)")
	if (enable) then
		f:EnableMouse(enable == "on")
		if (enable == "off") then
			DEFAULT_CHAT_FRAME:AddMessage("LootWatch frame locked.", unpack(color.text))
		elseif (enable == "on") then
			DEFAULT_CHAT_FRAME:AddMessage("LootWatch frame unlocked.", unpack(color.text))
		elseif (enable == "reset") then
			f:SetUserPlaced(false)
			f:ClearAllPoints()
			f:SetPoint("CENTER", "UIParent", "CENTER", 0, 0)
			DEFAULT_CHAT_FRAME:AddMessage("LootWatch frame reset.", unpack(color.text))
		end
		return
	end

	-- if nothing else works, show help
	for _, line in ipairs(helptext) do
		DEFAULT_CHAT_FRAME:AddMessage(line, unpack(color.text));
	end
end

-- hooks and frame functions
LootWatch_Old_QuestWatch_Update = QuestWatch_Update
function LootWatch_QuestWatch_Update()
	LootWatch_Old_QuestWatch_Update()
	self:RequestUpdate()
end
QuestWatch_Update = LootWatch_QuestWatch_Update

-- Initial setup
do
	f = CreateFrame("Frame", "LootWatchFrame", UIParent)
	e = CreateFrame("Frame", "LootWatchEventFrame", UIParent)
	e:SetScript("OnLoad", function() LootWatch:OnLoad() end)
	e:SetScript("OnEvent", function() LootWatch:OnEvent() end)
	
	f:SetMovable(true)
	f:SetWidth(280)
	f:SetHeight(20)
	f:SetPoint("RIGHT", UIParent, "RIGHT")
	f.title = f:CreateFontString(nil, "BORDER", "GameFontHighlight")
	f.title:SetPoint("TOPLEFT", f, "TOPLEFT")
	f.title:SetText("Inventory Items:")
	f.row = {}
	f.row[1] = f:CreateFontString(nil, "BORDER", "GameFontHighlight")
	f.row[1]:SetPoint("TOPLEFT", f.title, "BOTTOMLEFT")
	f.row[1].frame = CreateFrame("Frame", nil, f)
	f.row[1].frame:EnableMouse(true)
	f.row[1].frame:SetAllPoints(f.row[1])
	for i=2,50 do
		f.row[i] = f:CreateFontString(nil, "BORDER", "GameFontHighlight")
		f.row[i].frame = CreateFrame("Frame", nil, f)
		f.row[i].frame:EnableMouse(true)
	end
	
	-- slash commands
	SlashCmdList["LOOTWATCH"] = LootWatch_Command
	SLASH_LOOTWATCH1 = "/lw"
	SLASH_LOOTWATCH2 = "/lootwatch"

	-- arrange list
	for i=2, items do
		f.row[i]:SetPoint("TOPLEFT", f.row[i-1], "BOTTOMLEFT")
		f.row[i].frame:SetAllPoints(f.row[i])
	end

	-- make it movable
	f:RegisterForDrag("LeftButton")
	f:SetScript("OnDragStart", function() f:SetUserPlaced(true); f:StartMoving() end)
	f:SetScript("OnDragStop", function() f:StopMovingOrSizing() end)

	-- events
	e:RegisterEvent("VARIABLES_LOADED")
	e:SetScript("OnEvent", function(self, event, ...) LootWatch:OnEvent(self, event, ...) end)

	-- welcome
	DEFAULT_CHAT_FRAME:AddMessage("LootWatch loaded. /lw or /lootwatch", unpack(color.text));
end
